<!DOCTYPE html>
<html>
    <head>
        <title></title>
        <meta charset="utf-8">
        <style>
            *{margin:0;padding:0;border:none;}
        </style>
    </head>
    <body>
        <canvas id="canvas" class="canvas">当前浏览器不支持canvas标签，请更换浏览器重新尝试</canvas>
        <script>
            var canvas = document.getElementById('canvas');
            var context = canvas.getContext('2d');
            canvas.width = 520;
            canvas.height = 520;
            CanvasRenderingContext2D.prototype.cirRct = function (x, y, w, l, r, color)
            {
                this.beginPath();
                this.moveTo(x + r, y);
                this.lineTo(x + w - r, y);
                this.arcTo(x + w, y, x + w, y + r, r);
                this.lineTo(x + w, y + l - r);
                this.arcTo(x + w, y + l, x + w - r, y + l, r);
                this.lineTo(x + r, y + l);
                this.arcTo(x, y + l, x, y + l - r, r);
                this.lineTo(x, y + r);
                this.arcTo(x, y, x + r, y, r);
                this.fillStyle = color;
                this.closePath();
                this.fill();
                this.stroke();
            }
            context.cirRct(50, 50, 50, 60, 0, '#f00');
            
            var offobj = {
                a:function(){
                    // 在离屏 canvas 上绘制
                    var offscreencanvas = document.createElement('canvas');
                    // 宽高赋值为想要的图片尺寸
                    offscreencanvas.width = 52;
                    offscreencanvas.height = 62;
                    // 裁剪
                    offscreencanvas.getContext('2d').cirRct(1, 1, 50, 60, 0, '#f00');
                    return offscreencanvas;
                },
                b:function(){
                    // 在离屏 canvas 上绘制
                    var offscreencanvas = document.createElement('canvas');
                    // 宽高赋值为想要的图片尺寸
                    offscreencanvas.width = 32;
                    offscreencanvas.height = 32;
                    // 裁剪
                    offscreencanvas.getContext('2d').cirRct(1, 1, 30, 30, 0, '#ff0');
                    return offscreencanvas;
                }
            }
            // 在视图canvas中绘制
            context.drawImage(offobj.a(), 111, 111);
            context.drawImage(offobj.a(), 211, 211);
            context.drawImage(offobj.b(), 311, 311);
        </script>
        
        
	<div>
            <style>
                #cas{display: block;background-color:rgba(0,0,0,0);margin:auto;border:1px solid;}
            </style>
            <canvas id='cas' width="800" height="600">浏览器不支持canvas</canvas>
            <div style="text-align:center">使用了缓存，1000个圈圈对象也不卡</div>
	</div>
        <script>
            // stats.js - http://github.com/mrdoob/stats.js
var Stats=function(){function f(a,e,b){a=document.createElement(a);a.id=e;a.style.cssText=b;return a}function l(a,e,b){var c=f("div",a,"padding:0 0 3px 3px;text-align:left;background:"+b),d=f("div",a+"Text","font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px;color:"+e);d.innerHTML=a.toUpperCase();c.appendChild(d);a=f("div",a+"Graph","width:74px;height:30px;background:"+e);c.appendChild(a);for(e=0;74>e;e++)a.appendChild(f("span","","width:1px;height:30px;float:left;opacity:0.9;background:"+
b));return c}function m(a){for(var b=c.children,d=0;d<b.length;d++)b[d].style.display=d===a?"block":"none";n=a}function p(a,b){a.appendChild(a.firstChild).style.height=Math.min(30,30-30*b)+"px"}var q=self.performance&&self.performance.now?self.performance.now.bind(performance):Date.now,k=q(),r=k,t=0,n=0,c=f("div","stats","width:80px;opacity:0.9;cursor:pointer");c.addEventListener("mousedown",function(a){a.preventDefault();m(++n%c.children.length)},!1);var d=0,u=Infinity,v=0,b=l("fps","#0ff","#002"),
A=b.children[0],B=b.children[1];c.appendChild(b);var g=0,w=Infinity,x=0,b=l("ms","#0f0","#020"),C=b.children[0],D=b.children[1];c.appendChild(b);if(self.performance&&self.performance.memory){var h=0,y=Infinity,z=0,b=l("mb","#f08","#201"),E=b.children[0],F=b.children[1];c.appendChild(b)}m(n);return{REVISION:14,domElement:c,setMode:m,begin:function(){k=q()},end:function(){var a=q();g=a-k;w=Math.min(w,g);x=Math.max(x,g);C.textContent=(g|0)+" MS ("+(w|0)+"-"+(x|0)+")";p(D,g/200);t++;if(a>r+1E3&&(d=Math.round(1E3*
t/(a-r)),u=Math.min(u,d),v=Math.max(v,d),A.textContent=d+" FPS ("+u+"-"+v+")",p(B,d/100),r=a,t=0,void 0!==h)){var b=performance.memory.usedJSHeapSize,c=performance.memory.jsHeapSizeLimit;h=Math.round(9.54E-7*b);y=Math.min(y,h);z=Math.max(z,h);E.textContent=h+" MB ("+y+"-"+z+")";p(F,b/c)}return a},update:function(){k=this.end()}}};"object"===typeof module&&(module.exports=Stats);
        </script>
	<script>
            var stats = new Stats();
            stats.setMode(0);
            stats.domElement.style.position = 'absolute';
            stats.domElement.style.right = '0px';
            stats.domElement.style.top = '0px';
            document.body.appendChild( stats.domElement );

            var testBox = function(){
                var canvas = document.getElementById("cas"),
                    ctx = canvas.getContext('2d'),
                    borderWidth = 2,
                    Balls = [];
                var ball = function(x , y , vx , vy , useCache){
                    this.x = x;
                    this.y = y;
                    this.vx = vx;
                    this.vy = vy;
                    this.r = getZ(getRandom(20,40));
                    this.color = [];
                    this.cacheCanvas = document.createElement("canvas");
                    this.cacheCtx = this.cacheCanvas.getContext("2d");
                    this.cacheCanvas.width = 2 * this.r;
                    this.cacheCanvas.height = 2 * this.r;
                    var num = getZ(this.r / borderWidth);
                    for (var j = 0; j < num; j++){
                        this.color.push("rgba(" + getZ(getRandom(0, 255)) + "," + getZ(getRandom(0, 255)) + "," + getZ(getRandom(0, 255)) + ",1)");
                    }
                    this.useCache = useCache;
                    if (useCache){
                        this.cache();
                    }
                }

                function getZ(num){
                    var rounded;
                    rounded = (0.5 + num) | 0;
                    // A double bitwise not.
                    rounded = ~~ (0.5 + num);
                    // Finally, a left bitwise shift.
                    rounded = (0.5 + num) << 0;
                    return rounded;
                }

                ball.prototype = {
                    paint:function(ctx){
                        if (!this.useCache){
                            ctx.save();
                            var j = 0;
                            ctx.lineWidth = borderWidth;
                            for (var i = 1; i < this.r; i += borderWidth){
                                ctx.beginPath();
                                ctx.strokeStyle = this.color[j];
                                ctx.arc(this.x, this.y, i, 0, 2 * Math.PI);
                                ctx.stroke();
                                j++;
                            }
                            ctx.restore();
                        } else{
                            ctx.drawImage(this.cacheCanvas, this.x - this.r, this.y - this.r);
                        }
                    },
                    cache:function(){
                        this.cacheCtx.save();
                        var j = 0;
                        this.cacheCtx.lineWidth = borderWidth;
                        for (var i = 1; i < this.r; i += borderWidth){
                            this.cacheCtx.beginPath();
                            this.cacheCtx.strokeStyle = this.color[j];
                            this.cacheCtx.arc(this.r, this.r, i, 0, 2 * Math.PI);
                            this.cacheCtx.stroke();
                            j++;
                        }
                        this.cacheCtx.restore();
                    },
                    move:function(){
                        this.x += this.vx;
                        this.y += this.vy;
                        if (this.x > (canvas.width - this.r) || this.x < this.r){
                            this.x = this.x < this.r?this.r:(canvas.width - this.r);
                            this.vx = - this.vx;
                        }
                        if (this.y > (canvas.height - this.r) || this.y < this.r){
                            this.y = this.y < this.r?this.r:(canvas.height - this.r);
                            this.vy = - this.vy;
                        }
                        this.paint(ctx);
                    }
                }

                var Game = {
                    init:function(){
                        for (var i = 0; i < 2; i++){
                            var b = new ball(getRandom(0, canvas.width), getRandom(0, canvas.height), getRandom( - 10, 10), getRandom( - 10, 10), true)
                            Balls.push(b);
                        }
                    },
                    update:function(){
                        ctx.clearRect(0, 0, canvas.width, canvas.height);
                        for (var i = 0; i < Balls.length; i++){
                            Balls[i].move();
                        }
                    },
                    loop:function(){
                        var _this = this;
                        this.update();
                        stats.update();
                        RAF(function(){
                            _this.loop();
                        })
                    },
                    start:function(){
                        this.init();
                        this.loop();
                    }
                };
                window.RAF = (function(){
                    return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function (callback) {window.setTimeout(callback, 1000 / 60); };
                })();
                return Game;
            }();
            function getRandom(a, b){
                return Math.random() * (b - a) + a;
            }

            window.onload = function(){
                testBox.start();
            }
	</script>
        
        <script>
            //地图寻路
            var map = [
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 1, 1, 1, 1, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 1, 1, 0, 0, 0, 0],
    [0, 0, 2, 0, 1, 0, 1, 0, 0, 0],
    [0, 0, 0, 0, 1, 0, 0, 1, 0, 0],
    [0, 0, 0, 0, 1, 0, 1, 0, 0, 0],
    [0, 0, 0, 0, 1, 1, 0, 0, 0, 3],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
]
var paths = findPath([2, 3], [9, 6], map);
let newMap = map.slice();
paths.forEach(p => {
    newMap[p[1]][p[0]] = 4;
});
console.log("path:",paths)
console.log("map:",newMap)
function findPath(startPoint, targetPoint, map, configs) {
    // 配置
    configs = configs || {};
    // 判断地图格子是不是障碍
    var getIsObstacle = configs.getIsObstacle || ((tileValue) => {
        return tileValue == 1;
    });
    // 代价因子
    var getCostFactor = configs.getCostFactor || ((tileValue,currentPoint, parentPoint) => {
        if (!parentPoint) {
            return 1;
        }
        return currentPoint[0] != parentPoint[0] && currentPoint[1] !== parentPoint[1] ? 1.5: 1;
    });
    var
        row = map.length, //地图行高
        col = map[0].length, //地图列宽
        openList = [],//开放列表，存放待处理的节点
        closeList = [],//关闭列表，存放已处理的节点
        mapSet = map.map(d => new Array(col));
 
    // 创建节点
    function createNode(point, parentNode) {
        let x2 = point[0], y2 = point[1];
        let currentNode = mapSet[y2][x2];//获取当前节点
        // // 如果已存在，就不在创建 
        // if (currentNode) {
        //    // return currentNode;
        // }
        currentNode = {};
        let tileValue = map[y2][x2];// 地图对应的tile值
        let cost = getCostFactor(tileValue,point, parentNode ? parentNode.point : null);// 代价因子
        let isObstacle = getIsObstacle(tileValue, x2, y2);//是否障碍物
        //G值 = 父节点的G值 + 父节点到当前点的移动代价
        let g = parentNode ? (parentNode.g+cost): 0;//计算当前点与起始点的距离
       // let g = parentNode ? (Math.abs(parentNode.x - x2) + Math.abs(parentNode.y - y2) * cost) + parentNode.g : 0;//计算当前点与起始点的距离
        //H值 = 当前点到结束点的曼哈顿距离
        let h = Math.abs(targetPoint[0] - x2) + Math.abs(targetPoint[1] - y2);//计算当前点与目标点的距离          
        let f = g + h;//代价
 
        Object.assign(currentNode, {
            value: tileValue,
            isObstacle: isObstacle,
            g: g,
            h: h,
            f: f,
            x: x2,
            y: y2,
            point: point,
            parent: parentNode
        });
        mapSet[y2][x2] = currentNode;
        return currentNode;
    }
    // 获取目标节点
    function getTargetNodePath(currentNode) {
        // 从左上角开始计算
        //从行到列 
        rowLoop:
        for (let r = currentNode.y - 1, rlen = r + 3; r < rlen; r++) {
            colLoop:
            for (let c = currentNode.x - 1, clen = c + 3; c < clen; c++) {
                // 如果不在范围内，跳过
                if (!(c >= 0 && c < col && r >= 0 && r < row)) {
                    continue colLoop;
                }
                let point = [c, r];
                let node = createNode(point, currentNode);
                // 如果节点等于目标就直接返回
                if (node.x == targetPoint[0] && node.y == targetPoint[1]) {
                    return node;
                }
                // 如果不存在openlist列表中或closelist列表，并且是非障碍
                if (!node.isObstacle && openList.indexOf(node) == -1 && closeList.indexOf(node) == -1) {
                    openList.push(node);
                }
 
            }
        }
        //按最小代价升序排列
        openList.sort((a, b) => {
            return a.f - b.f;
        })
 
    }
    var startNode = createNode(startPoint, null), targetNode;
    openList.push(startNode);
    while (openList.length) {
        current = openList.shift();//获取当前列表最小代价节点
        closeList.push(current);
        targetNode = getTargetNodePath(current);
        if (targetNode) {
            break;
        }
    }
    // 如果不等空,就找出所有路径
    var paths = [];
    if (targetNode) {
        let current = targetNode.parent;
        while (current) {
            if (current !== startNode) {
                paths.unshift(current.point);
            }
            current = current.parent;
        }
    }
    return paths;
}
        </script>
    </body>
</html>
